import time
from copy import deepcopy
from threading import RLock
from collections import OrderedDict

_NOTSET = object()


class Cache(object):
    """
    一个基于内存且线程安全的先进先出的缓存类。支持的功能有：

    - 设定过期时间
    - 深复制缓存，外部赋值不会修改缓存中的相关内容

    缓存项存储在 "OrderedDict" 中，从而支持先进先出排序
    """

    def __init__(self):
        self._expire = {}
        self._lock = RLock()
        self._cache = OrderedDict()

    def __repr__(self):
        with self._lock:
            self._delete_expired()
            return "{}({})".format(self.__class__.__name__, list(self._copy().items()))

    def __len__(self):
        with self._lock:
            self._delete_expired()
            return len(self._cache)

    def __contains__(self, key):
        with self._lock:
            self._delete_expired()
            return key in self._cache

    def __iter__(self):
        with self._lock:
            self._delete_expired()
            yield from self.keys()

    def __next__(self):
        return next(iter(self._cache))

    def _copy(self):
        """
        返回缓存的一份深复制副本.
        :return: OrderedDict
        """
        return deepcopy(self._cache)

    def keys(self):
        """
        深复制一份缓存副本，返回副本中的未过期的缓存键名
        :return: dict_keys 缓存的键名
        """
        with self._lock:
            self._delete_expired()
            return self._copy().keys()

    def values(self):
        """
        深复制一份缓存副本，返回副本中的未过期的缓存键值
        :return: dict_values 缓存的键值
        """
        with self._lock:
            self._delete_expired()
            return self._copy().values()

    def items(self):
        """
        深复制一份缓存副本，返回副本中的内容转换为数组返回
        :return: dict_items 缓存对象数组
        """
        with self._lock:
            self._delete_expired()
            return self._copy().items()

    def size(self):
        """
        返回缓存中项目的个数
        :return: int
        """
        with self._lock:
            self._delete_expired()
            return len(self._cache)

    def clear(self):
        """
        清除整个缓存空间
        """
        with self._lock:
            self._cache.clear()
            self._expire.clear()

    def get(self, key, default=None):
        """
        返回缓存项的键值内容，如果键名不存在或已过期则返回 default 参数的值；
        :param key: (mixed) 缓存的键名.
        :param default: (mixed, optional) 如果键名不存在或已过期返回该参数设定的值，默认为None.
        :return: (mixed) The cached value
        """
        with self._lock:
            return self._get(key, default=default)

    def _get(self, key, default=None):
        try:
            self._delete_expired()
            value = self._cache[key]
        except KeyError:
            value = default

        return value

    def has(self, key):
        """
        判断缓存是否存在并且未过期
        :return: bool
        """
        with self._lock:
            return self._has(key)

    def _has(self, key):
        return self._get(key, default=_NOTSET) is not _NOTSET

    def set(self, key, value, ttl=0):
        """
        设置缓存（如果键名已存在，则覆盖对应的键值内容）
        :param key: (mixed) 缓存的键名
        :param value: (mixed) 缓存的键值
        :param ttl: (int, optional): 过期时间. 必须是大于等于0的数字. 默认是0.
        """
        if not isinstance(ttl, (int, float)):
            raise TypeError("过期时间必须是一个数字")

        if not ttl >= 0:
            raise ValueError("过期时间必须大于等于0")

        with self._lock:
            # 如果存在该键名，则从缓存空间及过期空间中删除该项后重新缓存
            if self._has(key):
                self._delete(key)

            self._cache[key] = value
            if ttl > 0:
                self._expire[key] = time.time() + ttl

    def delete(self, key):
        """
        删除缓存项，如果删除的键名存在则返回1，键名不存在则返回0
        :return: int
        """
        with self._lock:
            return self._delete(key)

    def _delete(self, key):
        count = 0

        # 删除缓存空间中的相关项
        try:
            del self._cache[key]
            count = 1
        except KeyError:
            pass

        # 删除过期空间中的相关项
        try:
            del self._expire[key]
        except KeyError:
            pass

        return count

    def _delete_expired(self):
        if not self._expire:
            return None

        # 使用一个静态的过期时间，以获得更好的一致性，而不是在每次迭代中使用一个新计算的时间戳。
        expire_time = time.time()
        _expires = deepcopy(self._expire)

        for key, expiration in _expires.items():
            if expiration <= expire_time:
                self._delete(key)

    def popitem(self):
        """
        删除过期的缓存，返回缓存队列中的第一个缓存项后，再从缓存队列中删除该缓存项
        :return:
        """
        with self._lock:
            try:
                key = next(self)
            except StopIteration:
                raise KeyError("popitem(): 缓存空间已无缓存项")

            value = self._cache[key]
            self._delete(key)

            return {'key': key, 'value': value}


# ==================== 带超时的缓存变量接口 ==================== #
c = Cache()


def keys():
    return list(c.keys())


def values():
    return list(c.values())


def items():
    ret = []
    for key, value in c.items():
        ret.append({'key': key, 'value': value})
    return ret


def size():
    return c.size()


def clear():
    c.clear()


def get(key, default=None):
    return c.get(key, default)


def has(key):
    return c.has(key)


def put(key, value, ttl=0):
    c.set(key, value, ttl)


def delete(key):
    return c.delete(key)


def popitem():
    return c.popitem()


if __name__ == '__main__':
    print('MeCache - More Efficient UiBot Extension')

    # put('c', {'a': 'a'})
    # put('b', 2)
    # put('a', 1)
    # put('过期a', '过期a', 2)
    # put('过期b', '过期b', 2)
    #
    # print('所有键名：{}'.format(keys()))
    # print('所有键值：{}'.format(values()))
    # print('缓存项是`a`否存在：{}'.format(has('a')))
    # print('缓存项是`aa`否存在：{}'.format(has('aa')))
    # print('缓存项个数：{}'.format(size()))
    # print('所有缓存项：{}'.format(items()))
    #
    # print('---------- 等待2秒 ----------')
    # time.sleep(2)
    #
    # print('过期后缓存项： {}'.format(items()))
    #
    # print('---------- 删除缓存 `c` ----------')
    # delete('c')
    # print('删除后缓存项： {}'.format(items()))
    # print('删除后获取C： {}'.format(get('c')))
    #
    # print('---------- 弹出缓存项  ----------')
    # pop = popitem()
    # print('弹出的缓存项： {}'.format(pop))
    # print('弹出后缓存项： {}'.format(items()))
    #
    # print('---------- 清空缓存 ----------')
    # clear()
    #
    # print('缓存项个数：{}'.format(size()))
    # print('所有缓存项：{}'.format(items()))
